using Mono.Cecil.Cil;

namespace Cil.Ast.Cil;

internal static class OpCodesEx
{
    public static OperationType ToOperationType(this Code opcode) =>
        opcode switch
        {
            Code.Beq => OperationType.Equal,
            Code.Bge => OperationType.GreaterOrEqual,
            Code.Bge_Un => OperationType.GreaterOrEqual,
            Code.Bgt => OperationType.Greater,
            Code.Bgt_Un => OperationType.Greater,
            Code.Ble => OperationType.LessOrEqual,
            Code.Ble_Un => OperationType.LessOrEqual,
            Code.Blt => OperationType.Less,
            Code.Blt_Un => OperationType.Less,
            Code.Bne_Un => OperationType.NotEqual,
            Code.Add => OperationType.Add,
            Code.Add_Ovf => OperationType.Add,
            Code.Add_Ovf_Un => OperationType.Add,
            Code.Sub => OperationType.Subtract,
            Code.Sub_Ovf => OperationType.Subtract,
            Code.Sub_Ovf_Un => OperationType.Subtract,
            Code.Mul => OperationType.Multiply,
            Code.Mul_Ovf => OperationType.Multiply,
            Code.Mul_Ovf_Un => OperationType.Multiply,
            Code.Div => OperationType.Divide,
            Code.Div_Un => OperationType.Divide,
            Code.Rem => OperationType.Remainder,
            Code.Rem_Un => OperationType.Remainder,
            Code.And => OperationType.LogicalAnd,
            Code.Or => OperationType.LogicalOr,
            Code.Xor => OperationType.LogicalXor,
            Code.Shl => OperationType.ShiftLeft,
            Code.Shr => OperationType.ShiftRight,
            Code.Shr_Un => OperationType.ShiftRight,
            Code.Neg => OperationType.UnaryMinus,
            Code.Not => OperationType.Not,
            Code.Ceq => OperationType.Equal,
            Code.Cgt => OperationType.Greater,
            Code.Cgt_Un => OperationType.Greater,
            Code.Clt => OperationType.Less,
            Code.Clt_Un => OperationType.Less,
            _ => throw new ArgumentException($"Unexpected opcode: {opcode}.")
        };

    public static OverflowCheckType ToOverflowCheck(this Code opcode) =>
        opcode switch
        {
            Code.Conv_Ovf_I1_Un => OverflowCheckType.Enabled,
            Code.Conv_Ovf_I2_Un => OverflowCheckType.Enabled,
            Code.Conv_Ovf_I4_Un => OverflowCheckType.Enabled,
            Code.Conv_Ovf_I8_Un => OverflowCheckType.Enabled,
            Code.Conv_Ovf_U1_Un => OverflowCheckType.Enabled,
            Code.Conv_Ovf_U2_Un => OverflowCheckType.Enabled,
            Code.Conv_Ovf_U4_Un => OverflowCheckType.Enabled,
            Code.Conv_Ovf_U8_Un => OverflowCheckType.Enabled,
            Code.Conv_Ovf_I_Un => OverflowCheckType.Enabled,
            Code.Conv_Ovf_U_Un => OverflowCheckType.Enabled,
            Code.Conv_Ovf_I1 => OverflowCheckType.Enabled,
            Code.Conv_Ovf_U1 => OverflowCheckType.Enabled,
            Code.Conv_Ovf_I2 => OverflowCheckType.Enabled,
            Code.Conv_Ovf_U2 => OverflowCheckType.Enabled,
            Code.Conv_Ovf_I4 => OverflowCheckType.Enabled,
            Code.Conv_Ovf_U4 => OverflowCheckType.Enabled,
            Code.Conv_Ovf_I8 => OverflowCheckType.Enabled,
            Code.Conv_Ovf_U8 => OverflowCheckType.Enabled,
            Code.Conv_Ovf_I => OverflowCheckType.Enabled,
            Code.Conv_Ovf_U => OverflowCheckType.Enabled,
            Code.Add_Ovf => OverflowCheckType.Enabled,
            Code.Add_Ovf_Un => OverflowCheckType.Enabled,
            Code.Mul_Ovf => OverflowCheckType.Enabled,
            Code.Mul_Ovf_Un => OverflowCheckType.Enabled,
            Code.Sub_Ovf => OverflowCheckType.Enabled,
            Code.Sub_Ovf_Un => OverflowCheckType.Enabled,
            _ => OverflowCheckType.Disabled
        };

    public static bool IsUnsigned(this Code opcode) =>
        opcode switch
        {
            Code.Cgt_Un => true,
            Code.Clt_Un => true,
            Code.Bne_Un => true,
            Code.Bge_Un => true,
            Code.Bgt_Un => true,
            Code.Ble_Un => true,
            Code.Blt_Un => true,
            Code.Bne_Un_S => true,
            Code.Bge_Un_S => true,
            Code.Bgt_Un_S => true,
            Code.Ble_Un_S => true,
            Code.Blt_Un_S => true,
            Code.Div_Un => true,
            Code.Rem_Un => true,
            Code.Shr_Un => true,
            Code.Add_Ovf_Un => true,
            Code.Mul_Ovf_Un => true,
            Code.Sub_Ovf_Un => true,
            Code.Conv_R_Un => true,
            Code.Conv_Ovf_I1_Un => true,
            Code.Conv_Ovf_I2_Un => true,
            Code.Conv_Ovf_I4_Un => true,
            Code.Conv_Ovf_I8_Un => true,
            Code.Conv_Ovf_U1_Un => true,
            Code.Conv_Ovf_U2_Un => true,
            Code.Conv_Ovf_U4_Un => true,
            Code.Conv_Ovf_U8_Un => true,
            Code.Conv_Ovf_I_Un => true,
            Code.Conv_Ovf_U_Un => true,
            _ => false
        };

    public static OpCode ToOpCode(this OperationType optype, bool isUnsigned = false, OverflowCheckType overflowCheckType = OverflowCheckType.Disabled)
    {
        if (overflowCheckType == OverflowCheckType.Enabled)
        {
            /*
                case Code.Conv_Ovf_I1_Un:
                case Code.Conv_Ovf_I2_Un:
                case Code.Conv_Ovf_I4_Un:
                case Code.Conv_Ovf_I8_Un:
                case Code.Conv_Ovf_U1_Un:
                case Code.Conv_Ovf_U2_Un:
                case Code.Conv_Ovf_U4_Un:
                case Code.Conv_Ovf_U8_Un:
                case Code.Conv_Ovf_I_Un:
                case Code.Conv_Ovf_U_Un:
                case Code.Conv_Ovf_I1:
                case Code.Conv_Ovf_U1:
                case Code.Conv_Ovf_I2:
                case Code.Conv_Ovf_U2:
                case Code.Conv_Ovf_I4:
                case Code.Conv_Ovf_U4:
                case Code.Conv_Ovf_I8:
                case Code.Conv_Ovf_U8:
                case Code.Conv_Ovf_I:
                case Code.Conv_Ovf_U:
            */
            if (isUnsigned)
            {
                switch (optype)
                {
                    case OperationType.Add:
                        return OpCodes.Add_Ovf_Un;
                    case OperationType.Multiply:
                        return OpCodes.Mul_Ovf_Un;
                    case OperationType.Subtract:
                        return OpCodes.Mul_Ovf_Un;
                }
            }
            else
            {
                switch (optype)
                {
                    case OperationType.Add:
                        return OpCodes.Add_Ovf;
                    case OperationType.Multiply:
                        return OpCodes.Mul_Ovf;
                    case OperationType.Subtract:
                        return OpCodes.Mul_Ovf;
                }
            }
        }
        else
        {
            switch (optype)
            {
                case OperationType.Equal:
                    return OpCodes.Beq;

                case OperationType.BranchWhenEqual:
                    return OpCodes.Ceq;

                case OperationType.GreaterOrEqual:
                    return isUnsigned ? OpCodes.Bge_Un : OpCodes.Bge;

                case OperationType.Greater:
                    return isUnsigned ? OpCodes.Bgt_Un : OpCodes.Bgt;

                case OperationType.BranchWhenGreater:
                    return isUnsigned ? OpCodes.Cgt_Un : OpCodes.Cgt;

                case OperationType.LessOrEqual:
                    return isUnsigned ? OpCodes.Ble_Un : OpCodes.Ble;

                case OperationType.Less:
                    return isUnsigned ? OpCodes.Blt_Un : OpCodes.Blt;

                case OperationType.BranchWhenLess:
                    return isUnsigned ? OpCodes.Clt_Un : OpCodes.Clt;

                case OperationType.NotEqual:
                    return OpCodes.Bne_Un;

                case OperationType.Add:
                    return OpCodes.Add;

                case OperationType.Subtract:
                    return OpCodes.Sub;

                case OperationType.Multiply:
                    return OpCodes.Mul;

                case OperationType.Divide:
                    return OpCodes.Div;

                case OperationType.Remainder:
                    return isUnsigned ? OpCodes.Rem_Un : OpCodes.Rem;

                case OperationType.LogicalOr:
                    return OpCodes.Or;

                case OperationType.LogicalXor:
                    return OpCodes.Xor;

                case OperationType.LogicalAnd:
                    return OpCodes.And;

                case OperationType.ShiftLeft:
                    return OpCodes.Shl;

                case OperationType.ShiftRight:
                    return isUnsigned ? OpCodes.Shr_Un : OpCodes.Shr;

                case OperationType.Not:
                    return OpCodes.Not;

                case OperationType.UnaryMinus:
                    return OpCodes.Neg;
            }
        }

        throw new ArgumentException("Arguments contains wrong instruction");
    }
}